import { Game, game } from "cc";
import { EngineConfig } from "./EngineConfig";
import { EventManager } from "./EventManager";
import { GameWebSocket, GameWebSocketStatus } from "./GameWebSocket";
import ServerTimer from "./ServerTimer";
import { Logger } from "./Logger";
import SocketRequestData from "./SocketRequestData";
import { AppConfig } from "../config/AppConfig";
import { UIManager } from "./UIManager";
import { ProtoManager, WsSendData } from "../lib/net/protobuf/ProtoManager";
import proto from "../../src/lib/net/protobuf/jhaoproto.js"
import { ViewTip } from "../view/ViewTip";
import VersionManager from "./VersionManager";


/**
 * 游戏socket通信，断线重连，处理消息等
 */
export class GameSocketClient {

    private socket: GameWebSocket;

    public url: string; //服务器地址 
    public isConnected: boolean = false;
    private isConnecting: boolean = false;

    private pingCount: number = 0;  //ping计数器
    private pingInteval: number = 3 * AppConfig.instance.frameRate;
    private pingTimeOutId;  //ping 时间超时处理器
    private reconnectCount: number = 0; //重连计数器
    private reconnectTimes: number = 0;//重连次数
    private isNeedReconnect: boolean = false; //是否需要重连
    private maxReconnectTimes: number = 3; //最大重连次数 

    private requestMsgQue: Array<SocketRequestData> = [];
    private requestMsgMap: Map<number, SocketRequestData> = new Map<number, SocketRequestData>(); //请求信息数据


    private reqOnlyId: number = 1; //
    private checkReqOnlyId() {
        let abc: number = this.reqOnlyId;
        this.reqOnlyId++;
        if (this.reqOnlyId >= Number.MAX_SAFE_INTEGER) {
            this.reqOnlyId = 1;
        }
        return abc;
    }


    public init(url: string) {
        this.url = url;
        this.socket = new GameWebSocket();
        EventManager.instance.clear(this);
        EventManager.instance.on(EngineConfig.Event_Type.Event_FrameUpdate, this.onFrameUpdate, this)
        this.isConnected = false;
        this.isConnecting = false;
        this.socket.SetCallBacks(this.onConnect, this.onClose, this.onSocketData, this.onError, this);
        this.connect()
    }

    /**
     * 链接 服务器
     */
    private connect() {
        // Logger.log("connnect===11=", nowSoskcetUrl, this.isConnected, this.isConnecting)
        if (this.isConnected || this.isConnecting) return;
        Logger.log("connnect====", this.url, this.isConnected, this.isConnecting)
        this.isConnecting = true;
        this.socket.connect(this.url);
    }


    /**
     * 链接成功回调
     * @param event 
     */
    private onConnect() {
        this.isConnected = true;
        this.isConnecting = false;
        this.reconnectTimes = 0;
        this.clearTimeOutId()
        this.pingCount = this.pingInteval;
        if (this.isNeedReconnect) {
            Logger.log("gamesocket==onConnect=isNeedReconnect==", this.isNeedReconnect)
            this.clearReqMsg();
        } else {
            Logger.log("onConnect=====", this.requestMsgQue)
            this.checkSendPack();
        }
        EventManager.instance.dispatch(EngineConfig.Event_Type.Event_Socket_Connected, this.isNeedReconnect);
    }

    //创建心跳包
    private createPing() {
        let send_data: proto.JhaoProto.IReqPing = {
            version: VersionManager.instance.nowVersion
        }
        let send_obj: WsSendData = {
            cmd_index: proto.JhaoProto.CmdIndex.Ping,
            en_proto_ctor: proto.JhaoProto.ReqPing,
            data: send_data,
            callback: (respData: proto.JhaoProto.BaseMsg) => {
                if (respData.errorCode == proto.JhaoProto.ErrorCodeWs.Suc) {
                    let respProto: proto.JhaoProto.RespPing = ProtoManager.instance.decode(proto.JhaoProto.RespPing, respData.data)
                    ServerTimer.setServerTime(respProto.t);
                } else {
                    UIManager.instance.showTip(respData.errorMsg, ViewTip.TipType.Fail);
                }
            }
        }
        this.sendToSocket(send_obj);
    }

    /**心跳 */
    private pingRequest() {
        if (this.isConnected) {
            this.createPing();
            clearTimeout(this.pingTimeOutId)
            this.pingTimeOutId = setTimeout(() => {
                if (this.isConnected) {
                    this.closeSocket(true)
                }
            }, this.pingInteval + 1 * 1000)
        }
    }

    /**
     * 消息接收处理
     * @param socketData 
     */
    private onSocketData(socketData: ArrayBuffer) {
        this.clearTimeOutId()
        try {
            let byteArray: Uint8Array = new Uint8Array(socketData);
            let baseMsg: proto.JhaoProto.BaseMsg = ProtoManager.instance.decode(proto.JhaoProto.BaseMsg, byteArray)
            let socketReqData: SocketRequestData | undefined = this.requestMsgMap.get(baseMsg.cmdOrder);
            Logger.log("onSocketData==", baseMsg, baseMsg.cmdIndex)
            if (socketReqData) {
                this.requestMsgMap.delete(baseMsg.cmdOrder)
                if (socketReqData.data.callback) {
                    socketReqData.data.callback(baseMsg)
                } else {
                    EventManager.instance.dispatch(EngineConfig.Event_Type.Event_Socket_msg, baseMsg)
                }
            } else {
                EventManager.instance.dispatch(EngineConfig.Event_Type.Event_Socket_msg, baseMsg)
            }
        } catch (error) {
            Logger.log("onSocketData error", this.url, error)
        }
    }

    /**
     * 外部接口调用发命令到服务器
     * @param sendData 
     */
    public sendToSocket(sendData: WsSendData) {
        let reqData: SocketRequestData = new SocketRequestData();
        reqData.id = this.checkReqOnlyId();
        reqData.data = sendData;
        this.requestMsgQue.push(reqData);
        // if (sendData.cmd_index != proto.JhaoProto.CmdIndex.Ping) {
        // Logger.log("sendToSocket===", reqData)
        // }
        if (this.isConnected) {
            this.checkSendPack();
        }
        else {
            if (!this.isConnecting) {
                this.connect();
            }
        }
    }

    /**
     * 检测发送包
     */
    private checkSendPack() {
        if (this.requestMsgQue.length > 0) {
            let socketData: SocketRequestData = this.requestMsgQue.shift();
            if (this.isConnected) {
                let data: Uint8Array = this.createSendPack(socketData)
                this.socket.send(data);
            } else {
                this.reConnect();
            }
        }
    }

    /**
     * 创建发送包
     * @param protoPack 
     */
    private createSendPack(socketData: SocketRequestData): Uint8Array {
        let send_data: proto.JhaoProto.IBaseMsg = {
            cmdOrder: socketData.id,
            cmdIndex: socketData.data.cmd_index,
            timeUpload: ServerTimer.now,
            data: ProtoManager.instance.encode(socketData.data.en_proto_ctor, socketData.data.data)
        }
        Logger.log("createSendPack==", send_data.cmdIndex, socketData.data.en_proto_ctor)
        this.requestMsgMap.set(socketData.id, socketData)
        return ProtoManager.instance.encode(proto.JhaoProto.BaseMsg, send_data)
    }

    private onFrameUpdate(dt: number) {
        this.checkPing();
        this.checkReConnect();
    }

    private checkPing() {
        if (this.isConnected) {
            this.pingCount--;
            if (this.pingCount <= 0) {
                this.pingCount = this.pingInteval;
                this.pingRequest();
            }
        }
    }

    private checkReConnect() {
        if (this.isNeedReconnect) {
            this.reconnectCount--;
            if (this.reconnectCount == 0) {
                this.connect();
            }
        }
    }

    private reConnect() {
        this.reconnectTimes++;
        if (this.reconnectTimes >= this.maxReconnectTimes) {
            UIManager.instance.showCheck("网络异常，请稍后再试试", () => {
                this.reconnectTimes = 0
                this.reconnectCount = 1
                this.connect()
            })
        } else {
            this.reconnectCount = 1 * AppConfig.instance.frameRate;
        }
    }

    /**
    * 链接错误
    * @param e 
    */
    private onError(e: Event) {
        Logger.log("GameSocket onError");
        this.isConnecting = false;
    }


    /**
     *  服务器断开链接
     * @param e 
     */
    private onClose() {
        // Logger.log("onClose===", this.isConnected, this.isReconnecting)
        this.isConnected = false;
        this.isConnecting = false;
        this.isNeedReconnect = true;
        this.clearTimeOutId()
        Logger.log("GameSocket onClose");
        this.reConnect();
    }

    /**
     * 清理计时器
     */
    private clearTimeOutId() {
        clearTimeout(this.pingTimeOutId);
    }

    /**
     * 清理请求队列数据
     */
    private clearReqMsg() {
        this.requestMsgQue = [];
        this.requestMsgMap.clear();
    }

    /**
     *  主动断开链接
     * @param isNeedReconnect  是否需要重连
     * @param clearReq 
     */
    public closeSocket(isNeedReconnect: boolean = false, clearReq: boolean = true) {
        if (isNeedReconnect) {
            UIManager.instance.showLoadingIcon()
        }
        if (clearReq) {
            this.clearReqMsg();
        }
        this.isConnecting = false;
        this.isConnected = false;
        this.isNeedReconnect = isNeedReconnect;
        this.clearTimeOutId()
        Logger.log("closeSocket==", this.isNeedReconnect, this.socket.readyState, GameWebSocketStatus.CLOSED)
        this.socket.close();
        this.socket = null;
        this.init(this.url);
    }

}